
package com.javacoo.junit.generator.config.xml;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

import com.javacoo.junit.generator.config.*;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import com.javacoo.junit.generator.exception.XMLParserException;
import com.javacoo.junit.generator.internal.ObjectFactory;

import static com.javacoo.junit.generator.internal.util.StringUtility.stringHasValue;
import static com.javacoo.junit.generator.internal.util.messages.Messages.getString;

/**
 * 配置文件解析类
 * <li>此类将配置文件解析为新的配置API</li>
 * @author duanyong@jccfc.com
 * @date 2020/12/31 16:39
 */
public class JunitGeneratorConfigurationParser {
    private final Properties extraProperties;
    private final Properties configurationProperties;

    public JunitGeneratorConfigurationParser(Properties extraProperties) {
        super();
        if (extraProperties == null) {
            this.extraProperties = new Properties();
        } else {
            this.extraProperties = extraProperties;
        }
        configurationProperties = new Properties();
    }
    /**
     * 解析配置
     * <p>说明:</p>
     * <li></li>
     * @author duanyong@jccfc.com
     * @date 2021/1/1 22:32
     * @param rootNode 根节点
     * @return Configuration 配置对象
     */
    public Configuration parseConfiguration(Element rootNode)
            throws XMLParserException {

        Configuration configuration = new Configuration();

        NodeList nodeList = rootNode.getChildNodes();
        for (int i = 0; i < nodeList.getLength(); i++) {
            Node childNode = nodeList.item(i);

            if (childNode.getNodeType() != Node.ELEMENT_NODE) {
                continue;
            }

            if ("properties".equals(childNode.getNodeName())) {
                parseProperties(childNode);
            } else if ("classPathEntry".equals(childNode.getNodeName())) {
                parseClassPathEntry(configuration, childNode);
            } else if ("context".equals(childNode.getNodeName())) {
                parseContext(configuration, childNode);
            }
        }

        return configuration;
    }
    /**
     * 解析properties节点
     * <p>说明:</p>
     * <li></li>
     * @author duanyong@jccfc.com
     * @date 2021/1/1 22:34
     * @param node 节点
     */
    protected void parseProperties(Node node)
            throws XMLParserException {
        Properties attributes = parseAttributes(node);
        String resource = attributes.getProperty("resource");
        String url = attributes.getProperty("url");

        if (!stringHasValue(resource)
                && !stringHasValue(url)) {
            throw new XMLParserException(getString("RuntimeError.14"));
        }

        if (stringHasValue(resource)
                && stringHasValue(url)) {
            throw new XMLParserException(getString("RuntimeError.14"));
        }

        URL resourceUrl;

        try {
            if (stringHasValue(resource)) {
                resourceUrl = ObjectFactory.getResource(resource);
                if (resourceUrl == null) {
                    throw new XMLParserException(getString(
                            "RuntimeError.15", resource));
                }
            } else {
                resourceUrl = new URL(url);
            }

            InputStream inputStream = resourceUrl.openConnection().getInputStream();

            configurationProperties.load(inputStream);
            inputStream.close();
        } catch (IOException e) {
            if (stringHasValue(resource)) {
                throw new XMLParserException(getString(
                        "RuntimeError.16", resource));
            } else {
                throw new XMLParserException(getString(
                        "RuntimeError.17", url));
            }
        }
    }
    /**
     * 解析ClassPathEntry
     * <p>说明:</p>
     * <li></li>
     * @author duanyong@jccfc.com
     * @date 2021/1/1 22:35
     * @param configuration 配置对象
     * @param node 节点
     */
    protected void parseClassPathEntry(Configuration configuration, Node node) {
        Properties attributes = parseAttributes(node);

        configuration.addClasspathEntry(attributes.getProperty("location"));
    }
    /**
     * 解析上下文节点
     * <p>说明:</p>
     * <li></li>
     * @author duanyong@jccfc.com
     * @date 2021/1/1 22:36
     * @param configuration 配置对象
     * @param node 节点
     */
    private void parseContext(Configuration configuration, Node node) {

        Properties attributes = parseAttributes(node);
        String id = attributes.getProperty("id");


        Context context = new Context();
        context.setId(id);


        configuration.addContext(context);

        NodeList nodeList = node.getChildNodes();
        for (int i = 0; i < nodeList.getLength(); i++) {
            Node childNode = nodeList.item(i);

            if (childNode.getNodeType() != Node.ELEMENT_NODE) {
                continue;
            }

            if ("property".equals(childNode.getNodeName())) {
                parseProperty(context, childNode);
            } else if ("template".equals(childNode.getNodeName())) {
                parseTemplate(context, childNode);
            } else if ("classList".equals(childNode.getNodeName())) {
                parseClassList(context, childNode);
            }

        }
    }
    /**
     * 解析Properties
     * <p>说明:</p>
     * <li></li>
     * @author duanyong@jccfc.com
     * @date 2021/1/1 22:40
     * @param propertyHolder
     * @param node 节点
     */
    protected void parseProperty(PropertyHolder propertyHolder, Node node) {
        Properties attributes = parseAttributes(node);

        String name = attributes.getProperty("name");
        String value = attributes.getProperty("value");

        propertyHolder.addProperty(name, value);
    }
    /**
     * 解析模板
     * <p>说明:</p>
     * <li></li>
     * @author duanyong@jccfc.com
     * @date 2021/1/1 22:42
     * @param context 上下对象
     * @param childNode 子节点
     */
    private void parseTemplate(Context context, Node childNode) {
        TemplateConfiguration templateConfiguration = new TemplateConfiguration();

        Properties attributes = parseAttributes(childNode);

        String templatePath = attributes.getProperty("templatePath");
        if (stringHasValue(templatePath)) {
            templateConfiguration.setTemplatePath(templatePath);
        }

        String templateName = attributes.getProperty("templateName");
        if (stringHasValue(templateName)) {
            templateConfiguration.setTemplateName(templateName);
        }

        String templateHandlerName = attributes.getProperty("templateHandlerName");
        if (stringHasValue(templateHandlerName)) {
            templateConfiguration.setTemplateHandlerName(templateHandlerName);
        }

        context.setTemplateConfiguration(templateConfiguration);
    }
    /**
     * 解析类对象集合
     * <p>说明:</p>
     * <li></li>
     * @author duanyong@jccfc.com
     * @date 2021/1/1 22:43
     * @param context 上下对象
     * @param node 子节点
     */
    private void parseClassList(Context context, Node node) {
        NodeList nodeList = node.getChildNodes();
        for (int i = 0; i < nodeList.getLength(); i++) {
            Node childNode = nodeList.item(i);

            if (childNode.getNodeType() != Node.ELEMENT_NODE) {
                continue;
            }

            ClassConfiguration classConfiguration = new ClassConfiguration();

            if ("property".equals(childNode.getNodeName())) {
                parseProperty(classConfiguration, childNode);
            } else if ("class".equals(childNode.getNodeName())) {
                parseClass(classConfiguration, childNode);
            }
            context.getClassConfigurations().add(classConfiguration);
        }

    }
    /**
     * 解析类配置
     * <p>说明:</p>
     * <li></li>
     * @author duanyong@jccfc.com
     * @date 2021/1/1 22:56
     * @param classConfiguration 类配置对象
     * @param node 子节点
     */
    private void parseClass(ClassConfiguration classConfiguration, Node node) {
        Properties attributes = parseAttributes(node);

        String className = attributes.getProperty("className");
        if (stringHasValue(className)) {
            classConfiguration.setClassName(className);
        }
    }


    protected Properties parseAttributes(Node node) {
        Properties attributes = new Properties();
        NamedNodeMap nnm = node.getAttributes();
        for (int i = 0; i < nnm.getLength(); i++) {
            Node attribute = nnm.item(i);
            String value = parsePropertyTokens(attribute.getNodeValue());
            attributes.put(attribute.getNodeName(), value);
        }

        return attributes;
    }

    String parsePropertyTokens(String s) {
        final String OPEN = "${";
        final String CLOSE = "}";
        int currentIndex = 0;

        List<String> answer = new ArrayList<>();

        int markerStartIndex = s.indexOf(OPEN);
        if (markerStartIndex < 0) {
            // no parameter markers
            answer.add(s);
            currentIndex = s.length();
        }

        while (markerStartIndex > -1) {
            if (markerStartIndex > currentIndex) {
                // add the characters before the next parameter marker
                answer.add(s.substring(currentIndex, markerStartIndex));
                currentIndex = markerStartIndex;
            }

            int markerEndIndex = s.indexOf(CLOSE, currentIndex);
            int nestedStartIndex = s.indexOf(OPEN, markerStartIndex + OPEN.length());
            while (nestedStartIndex > -1 && markerEndIndex > -1 && nestedStartIndex < markerEndIndex) {
                nestedStartIndex = s.indexOf(OPEN, nestedStartIndex + OPEN.length());
                markerEndIndex = s.indexOf(CLOSE, markerEndIndex + CLOSE.length());
            }

            if (markerEndIndex < 0) {
                // no closing delimiter, just move to the end of the string
                answer.add(s.substring(markerStartIndex));
                currentIndex = s.length();
                break;
            }

            // we have a valid property marker...
            String property = s.substring(markerStartIndex + OPEN.length(), markerEndIndex);
            String propertyValue = resolveProperty(parsePropertyTokens(property));
            if (propertyValue == null) {
                // add the property marker back into the stream
                answer.add(s.substring(markerStartIndex, markerEndIndex + 1));
            } else {
                answer.add(propertyValue);
            }

            currentIndex = markerEndIndex + CLOSE.length();
            markerStartIndex = s.indexOf(OPEN, currentIndex);
        }

        if (currentIndex < s.length()) {
            answer.add(s.substring(currentIndex));
        }

        return String.join("", answer);
    }


    /**
     * This method resolve a property from one of the three sources: system properties,
     * properties loaded from the &lt;properties&gt; configuration element, and
     * "extra" properties that may be supplied by the Maven or Ant environments.
     *
     * <p>If there is a name collision, system properties take precedence, followed by
     * configuration properties, followed by extra properties.
     *
     * @param key property key
     * @return the resolved property.  This method will return null if the property is
     *     undefined in any of the sources.
     */
    private String resolveProperty(String key) {
        String property = System.getProperty(key);

        if (property == null) {
            property = configurationProperties.getProperty(key);
        }

        if (property == null) {
            property = extraProperties.getProperty(key);
        }

        return property;
    }
}
